﻿using System;
using System.Drawing;
using System.Collections.Generic;
using System.Text;

namespace NetShips {
    /// <summary>
    /// Třída zastupující loď.
    /// </summary>
    public class Ship: IDisposable {

        private static int[] sinAngel = { 0, 1, 0, -1 };
        private static int[] cosAngel = { 1, 0, -1, 0 };

        /// <summary>
        /// Výjimka vyvolána při pokusu vytvořit loď v místě, kam se neveleze.
        /// </summary>
        public ArgumentException NotAccomodateException = new ArgumentException("Loď se nevleze do pole");


        /// <summary>
        /// Výčet možných typů lodí.
        /// </summary>
        public enum ShipTypes_ENUM {
            /// <summary>
            /// Loď ve tvaru L.
            /// </summary>
            L,
            /// <summary>
            /// Loď ve tvaru T.
            /// </summary>
            T,
            /// <summary>
            /// Loď o délce 5ti čtverešků se dvěma komíny.
            /// </summary>
            Battleship,
            /// <summary>
            /// Loď o velikosti jednoho bodu.
            /// </summary>
            I,
            /// <summary>
            /// Loď o velikosti 2 bodů.
            /// </summary>
            Small
        };

        /// <summary>
        /// Možné úhly natočení lodi.
        /// </summary>
        public enum Directions_ENUM {
            /// <summary>
            /// Úhel 0°
            /// </summary>
            ANGLE_0,
            /// <summary>
            /// Úhel 90°
            /// </summary>
            ANGLE_90,
            /// <summary>
            /// Úhel 180°
            /// </summary>
            ANGLE_180,
            /// <summary>
            /// Úhel 270°
            /// </summary>
            ANGLE_270
        }

        /// <summary>
        /// Návratové hodnoty funkce VolnoProLoď
        /// </summary>
        public enum CanAccomodateReturn_ENUM {
            /// <summary>
            /// Volno pro loď - OK.
            /// </summary>
            OK,
            /// <summary>
            /// Bod je mimo hranice pole.
            /// </summary>
            OutOfField,
            /// <summary>
            /// V bezprostředním okolí Bodu je Loď.
            /// </summary>
            NearToShip
        }

        /// <summary>
        /// Počet ještě nesestřelených políček lodi.
        /// </summary>
        private int _state;
        /// <summary>
        /// Vektor lodi v nulovém otočení.
        /// </summary>
        private Point[] OrigVector = null;
        /// <summary>
        /// Na indexu 0 je pozice Lodě, a ostatní prvky pole ukazují relativní pozice dalších Bodů Lodi.
        /// </summary>
        private Point[] shipVector = null;
        /// <summary>
        /// Odkazy na instance Bodů v HracímPoli které patří této Lodi.
        /// </summary>
        private FieldPoint[] shipPoints = null;
        /// <summary>
        /// Pozice na které se nachází referenční bod Lodi.
        /// </summary>
        private Point _position;
        /// <summary>
        /// Toto je typ Bodu, který bude zobrazen pro celou Loď.
        /// </summary>
        private FieldPoint.FieldPointType_ENUM _filedPointTypes;
        /// <summary>
        /// Ukládá jakého typu je Loď.
        /// </summary>
        private ShipTypes_ENUM _shipType;
        /// <summary>
        /// Ukládá kolekci, jejíž je loď součástí.
        /// </summary>
        private ShipsCollection _collection = null;
        /// <summary>
        /// Udává zda bude loď zobrazena, nebo maskována jako Voda.
        /// </summary>
        private bool _visibility = false;

        private Directions_ENUM _direction = Directions_ENUM.ANGLE_0;

        private FieldPoint[,] _gameField;

        /// <summary>
        /// Na pozici <paramref name="pozice"/> vytvoří novou loď typu <paramref name="typ"/> umístěnou do hracího pole <paramref name="hracíPlocha"/>.
        /// </summary>
        /// <param name="typ">Typ vytvářené lodě.</param>
        /// <param name="pozRefBod">Souřadnice, kde má být umístěn referenční bod lodi. Jednotkou je index v 2D-poli.</param>
        /// <param name="hracíPlocha">2D-pole Bodů nad kterými bude nová Loď vytvořena.</param>
        /// <param name="úhel">Označuje otočení lodi.</param>
        public Ship(ShipTypes_ENUM typ, Point pozRefBod, FieldPoint[,] hracíPlocha, Directions_ENUM úhel) {

            _position = pozRefBod;
            _gameField = hracíPlocha;
            _shipType = typ;
            _direction = úhel;
            switch(typ) {
                case ShipTypes_ENUM.L:
                    Array.Resize<Point>(ref shipVector, 4);
                    Array.Resize<FieldPoint>(ref shipPoints, 4);

                    shipVector[0] = new Point(0, 0);
                    shipVector[1] = new Point(+1, 0);
                    shipVector[2] = new Point(-1, 0);
                    shipVector[3] = new Point(+1, -1);

                    break;
                case ShipTypes_ENUM.I:
                    Array.Resize<Point>(ref shipVector, 2);
                    Array.Resize<FieldPoint>(ref shipPoints, 2);

                    shipVector[0] = new Point(0, 0);
                    shipVector[1] = new Point(+1, 0);

                    break;
                case ShipTypes_ENUM.Battleship:
                    Array.Resize<Point>(ref shipVector, 7);
                    Array.Resize<FieldPoint>(ref shipPoints, 7);

                    shipVector[0] = new Point(0, 0);
                    shipVector[1] = new Point(-1, 0);
                    shipVector[2] = new Point(-2, 0);
                    shipVector[3] = new Point(1, 0);
                    shipVector[4] = new Point(2, 0);
                    shipVector[5] = new Point(1, -1);
                    shipVector[6] = new Point(-1, -1);

                    break;
                case ShipTypes_ENUM.Small:
                    Array.Resize<Point>(ref shipVector, 1);
                    Array.Resize<FieldPoint>(ref shipPoints, 1);

                    shipVector[0] = new Point(0, 0);

                    break;
                case ShipTypes_ENUM.T:
                    Array.Resize<Point>(ref shipVector, 4);
                    Array.Resize<FieldPoint>(ref shipPoints, 4);

                    shipVector[0] = new Point(0, 0);
                    shipVector[1] = new Point(-1, 0);
                    shipVector[2] = new Point(1, 0);
                    shipVector[3] = new Point(0, -1);

                    break;
                default:
                    break;
            }
            OrigVector = (Point[])shipVector.Clone();
            // upraví vektor na požadovaný úhel otočení Lodi
            Turn(ref shipVector, _direction);

            if(!_position.IsEmpty) {
                SetPosition();
            }
        }

        /// <summary>
        /// Pokusí se zobrazit loď na zadané pozici. Pokud se to podaří nastaví se stav na počet Bodů lodě
        /// - tím je připravena k boji. Když se loď nepodaří zobrazit (nevleze se), stav je nastaven na 0.
        /// </summary>
        /// <returns>Vrátí true pokud se podařilo nastavit pozici.</returns>
        public Boolean SetPosition() {
            // dočasně ukládá přpočtené souřadnice lodě podle vektoru.
            Point Poz;
            // pokud i jen jeden bod neprošel podmínkou pro zobrazení, bude
            // celá loď zobrazena jako chybná
            bool NevlezuSa = false;

            Delete();
            if(Colection != null)
                Colection.Refresh();

            // kontrola, jestli se vlezou všechny body do hracího pole
            for(int i = 0; i < shipVector.Length; i++) {
                Poz = Translate(_position, shipVector[i]);
                switch(CanAccomodate(Poz.X, Poz.Y, _gameField)) {
                    case CanAccomodateReturn_ENUM.OK:
                        shipPoints[i] = _gameField[Poz.X, Poz.Y];
                        break;
                    case CanAccomodateReturn_ENUM.OutOfField:
                        // zde se bod pro loď nevytváří, protože je mimo pole
                        shipPoints[i] = null;
                        NevlezuSa = true;
                        break;
                    case CanAccomodateReturn_ENUM.NearToShip:
                        shipPoints[i] = _gameField[Poz.X, Poz.Y];
                        NevlezuSa = true;
                        break;
                }
            }

            // bodům náležícím lodi se upraví typ na Loď případně Chyba
            if(NevlezuSa)
                _filedPointTypes = FieldPoint.FieldPointType_ENUM.Error;
            else
                _filedPointTypes = FieldPoint.FieldPointType_ENUM.Ship;

            // způsobí nastavení bodů na aktuální typ a stav
            this.Refresh();

            if(NevlezuSa) {
                _state = 0;
                return false;
            }
            else {
                _state = shipPoints.Length;
                return true;
            }


        }

        /// <summary>
        /// Znovu nastaví Body patřící k lodi na patřičný stav a typ.
        /// </summary>
        public void Refresh() {
            foreach(FieldPoint bod in shipPoints) {
                if(bod != null) {
                    bod.Type = _filedPointTypes;
                    if(bod.State == FieldPoint.State_ENUM.Hit || _visibility == true)
                        bod.Visibility = true;
                    else
                        bod.Visibility = false;
                }
            }
        }

        /// <summary>
        /// Udává pozici lodi pomocí souřadnic.
        /// </summary>
        public Point Pozice {
            get {
                return _position;
            }
            set {
                //VymazMe();
                _position = value;
                SetPosition();
            }
        }

        private void Delete() {
            for(int a = 0; a < shipPoints.Length; a++) {
                if(shipPoints[a] != null) {
                    shipPoints[a].Type = FieldPoint.FieldPointType_ENUM.Sea;
                    shipPoints[a] = null;
                }
            }
        }

        /// <summary>
        /// Přepočítá pozici bodu Start podle zadaného vektoru.
        /// </summary>
        /// <param name="Start">Počáteční bod.</param>
        /// <param name="vektor">Vektor posunu.</param>
        /// <returns>Souřadnice bodu vzniklého posunem.</returns>
        private Point Translate(Point Start, Point vektor) {
            return new Point(Start.X + vektor.X, Start.Y + vektor.Y);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="BodyLodě"></param>
        /// <param name="Úhel"></param>
        /// <remarks>
        /// <para>x' = x*cos(uhel) - y*sin(uhel)</para>
        /// <para>y' = x*sin(uhel) + y*cos(uhel)</para>
        /// </remarks>
        private void Turn(ref Point[] BodyLodě, Directions_ENUM Úhel) {
            for(int i = 0; i < BodyLodě.Length; i++) {
                Point tempBod = new Point();
                tempBod.X = OrigVector[i].X * cosAngel[(int)Úhel] - OrigVector[i].Y * sinAngel[(int)Úhel];
                tempBod.Y = OrigVector[i].X * sinAngel[(int)Úhel] + OrigVector[i].Y * cosAngel[(int)Úhel];
                BodyLodě[i] = tempBod;
            }
        }


        /// <summary>
        /// Když se bod nachází v hracím poli a není kolem něj žádný bod typu loď, vrátí True.
        /// </summary>
        /// <param name="posX">Xová souřadnice zkoumaného bodu.</param>
        /// <param name="posY">Yová souřadnice zkoumaného bodu.</param>
        /// <param name="hracíPlocha">Pole s Body hrací plochy.</param>
        /// <returns>True pokud je bod uvnitř pole a není kolem něj žádná Bod typu Loď.</returns>
        CanAccomodateReturn_ENUM CanAccomodate(int posX, int posY, FieldPoint[,] hracíPlocha) {
            if(posX >= 0 && posY >= 0 && posX <= hracíPlocha.GetUpperBound(0) && posY <= hracíPlocha.GetUpperBound(1)) {
                for(int x = -1; x <= 1; x++) {
                    for(int y = -1; y <= 1; y++) {
                        if(posX + x >= 0 && posY + y >= 0 && posX + x <= hracíPlocha.GetUpperBound(0) && posY + y <= hracíPlocha.GetUpperBound(1)) {
                            if(hracíPlocha[posX + x, posY + y].Type == FieldPoint.FieldPointType_ENUM.Ship)
                                // kolem zadaného bodu se nachází Loď nebo je mimo hrací pole
                                return CanAccomodateReturn_ENUM.NearToShip;
                        }
                        else
                            continue;
                    }
                }
            }
            else
                return CanAccomodateReturn_ENUM.OutOfField;

            return CanAccomodateReturn_ENUM.OK;
        }


        #region "Properties"
        /// <summary>
        /// Obsahuje informaci, zda je Loď zobrazena v pořádku, nebo s chybou (nevleze se do pole, nebo jí zavazá jiná Loď).
        /// </summary>
        public FieldPoint.FieldPointType_ENUM FieldPointsTypes {
            get {
                return _filedPointTypes;
            }
        }

        /// <summary>
        /// Vrací odkaz na kolekci lodí, jejíž je Loď součástí.
        /// </summary>
        public ShipsCollection Colection {
            set {
                _collection = value;
            }
            get {
                return _collection;
            }
        
        }

        /// <summary>
        /// Jakého typu je Loď.
        /// </summary>
        public ShipTypes_ENUM Type {
            get {
                return _shipType;
            }

        }

        /// <summary>
        /// Počet nesestřelených bodů.
        /// </summary>
        public int State {
            get {
                return _state;
            }
        }

        /// <summary>
        /// Pole odakzů na Body, ze kterých je daná Loď složena.
        /// </summary>
        public FieldPoint[] Points {
            get {
                return shipPoints;
            }
        }

        /// <summary>
        /// Nabývá jedné ze čtyř hodnot z Enum UhelOtoceni a udává jak je natočena Loď.
        /// </summary>
        public Directions_ENUM Direction {
            get {
                return _direction;
            }
            set {
                //VymazMe();

                _direction = value;
                if((int)_direction > 3)
                    _direction -= 4;

                this.Turn(ref shipVector, _direction);

                SetPosition();
            }
        }

        /// <summary>
        /// True pokud má být Loď zobrazena normálně, False pokud Loď bude maskována jako Voda.
        /// </summary>
        public bool Visibility {
            get {
                return _visibility;
            }
            set {
                _visibility = value;
            }
        }
        #endregion
        #region IDisposable Members

        /// <summary>
        /// Experimentální, raději nepoužívat.
        /// </summary>
        public void Dispose() {
            //this.VymazMe();
        }

        #endregion

        internal void Hit() {
            _state--;
        }
    }


}
